<script setup lang="ts">
import type { ComponentMeta } from '@/definitions/type'
import { useDesignTheme } from '@/hooks/useDesignTheme'
import { useDesignStore } from '@/stores'
import { Modal } from 'ant-design-vue'
import { computed, type CSSProperties, nextTick, ref, watch } from 'vue'

defineOptions({
  name: 'Preview',
})
const { themes } = useDesignTheme()
const designStore = useDesignStore()
const iframeRef = ref<HTMLIFrameElement>()
const setting = computed(() => {
  const isMobile = designStore.config.platform === 'mobile'
  return {
    title: isMobile ? '移动端预览' : 'PC端预览',
    iframeSize: isMobile ? { w: 375, h: 667 } : { w: 1920, h: 640 },
    modalWidth: isMobile ? 520 : 'fit-content',
  }
})
const cssVars = computed<CSSProperties>(() => {
  return {
    '--design-theme-color': themes.value.themeColor,
    '--design-theme-color-light': themes.value.themeColorLight,
    '--design-theme-color-lightest': themes.value.themeColorLightest,
  }
})
watch(() => designStore.config.preview.open, (n) => {
  if (n) {
    injectContent()
  }
})
async function injectContent() {
  await nextTick()
  if (!iframeRef.value) 
    return
  const renders = designStore.renders
  const htmls = renders.map((r) => {
    const node = document.querySelector(`#${r.id}`)
    if (node) {
      return node.outerHTML
    }
    return ''
  })
  const doc = iframeRef.value.contentDocument!
  doc.documentElement.dataset.name = import.meta.env.VITE_APP_CLASS_PREFIX
  const charsetMeta = doc.createElement('meta')
  charsetMeta.setAttribute('charset', 'UTF-8')
  const viewportMeta = doc.createElement('meta')
  viewportMeta.setAttribute('name', 'viewport')
  viewportMeta.setAttribute('content', 'width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover')
  doc.head.innerHTML = ''
  doc.head.append(charsetMeta, viewportMeta)
  doc.title = setting.value.title
  injectCSSVars(doc)
  const vendorScripts: string[] = []
  traverseNodes(vendorScripts).forEach((node) => {
    doc.head.append(node)
  })
  injectVendorScripts(doc, vendorScripts)
  doc.body.innerHTML = htmls.join('')
  hydrateForRenders(doc, renders)
  doc.body.style.overflow = 'auto'
}
function traverseNodes(vendorScripts: string[]) {
  const nodes: HTMLElement[] = []
  const count = document.head.childElementCount
  for (let i = 0; i < count; i++) {
    const node = document.head.children.item(i)
    if (node && isTargetNode(node)) {
      nodes.push(node.cloneNode(true) as HTMLElement)
    }
    if (node && isVendorScript(node)) {
      vendorScripts.push(normalizeVendorScript(node.src))
    }
  }
  function isTargetNode(node: Element) {
    const devCondition = import.meta.env.PROD ? false : node.nodeName === 'STYLE'
    return node.matches('[rel="stylesheet"]') || devCondition
  }
  function isVendorScript(node: Element): node is HTMLScriptElement {
    return node.matches('script[data-vendor]')
  }
  let base: string
  function normalizeVendorScript(src: string) {
    if (src.startsWith('http') || src.startsWith('//')) 
      return src
    if (!base) {
      const { origin } = window.location
      base = new URL(import.meta.env.BASE_URL, origin).toString().replace(/\/$/, '')
    }
    return base.concat(src)
  }
  return nodes
}
function injectVendorScripts(doc: Document, vendorScripts: string[]) {
  vendorScripts.forEach((src) => {
    const script = doc.createElement('script')
    script.src = src
    script.type = 'text/javascript'
    doc.head.append(script)
  })
}
async function hydrateForRenders(doc: Document, _renders: ComponentMeta[]) {
  const hydrateScript = doc.createElement('script')
  hydrateScript.type = 'text/javascript'
  const { default: scriptContent } = designStore.config.platform === 'mobile' ? await import('../../../public/js/index-mobile?raw') : await import('../../../public/js/index-pc?raw')
  hydrateScript.textContent = scriptContent
  doc.body.append(hydrateScript)
}
function injectCSSVars(doc: Document) {
  const styleEl = doc.createElement('style')
  const vars = cssVars.value
  const cssRules = Object.keys(vars).reduce((prev, next) => {
    // @ts-ignore
    prev.push(`${next}: ${vars[next]};`)
    return prev
  }, [] as string[]).join('')
  styleEl.textContent = `
    :root:root {
      ${cssRules}
    }
  `
  doc.head.append(styleEl) 
}
</script>

<template>
  <Modal
    v-model:open="designStore.config.preview.open" 
    :title="setting.title"
    :footer="null"
    :width="setting.modalWidth"
    :destroy-on-close="true"
  >
    <div class="w-full flex justify-center">
      <iframe
        ref="iframeRef" 
        frameborder="0"
        style="border: 1px solid #DDDDDD; box-sizing: content-box;"
        :width="setting.iframeSize.w" 
        :height="setting.iframeSize.h"
      />
    </div>
  </Modal>
</template>
